/*
 * DG (DG Gaming Engine)  - Server
 * Copyright (C) 2004  Bob Marks (marksie531@yahoo.com)
 * http://code.google.com/p/dng-gaming
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package com.dg.gaming.server;

import java.net.*;
import java.text.*;
import java.util.*;
import java.util.concurrent.*;

import com.dg.gaming.api.common.*;
import com.dg.gaming.server.data.*;
import org.xsocket.connection.*;
import org.xsocket.connection.IConnection.*;
import java.util.concurrent.atomic.AtomicInteger;
import com.dg.gaming.server.data.mysql.*;
import javax.net.ssl.*;

/**
 * <p>This is the all important DGServer class.</p>
 *
 * <p>When a client connects to a <code>DGServer</code> a new
 * <code>ServerConnectionThread</code> is created in its own Thread which will
 * handle all communciation between the server and the client. The connect
 * method returns the correct ServerConnectionThread to the server.</p>
 *
 * <p>If a server keeps state then the abstract <code>getGameModel()</code> method should
 * return the correct implementation of the <code>GameModel</code>. If a game should keep
 * state (this allows demanding games to run faster but means other users can't
 * join a table and watch a game) then this method should simply return null.</p>
 *
 * @author  Bob Marks
 * @version Beta 0.3
 */
public class GameServer {

    private static GameServer server = null;
    /** Logging */


    // Singleton instance of this server
    private static GameServer instance;

    /** Default server port. */
    public static final int DEFAULT_SERVER_PORT = 1790;

    /** Server port we are listening on. */
    protected int serverPort;

    /** Boolean to specify if this server tries to link to a master server. */
    protected boolean linkToMasterServer = false;


    /** Declare how a user connection. */
    protected IServerData dataConnection = null;

    private ServerLabels labels = ServerLabels.getInstance(); // convience link to server labels
    private long startTime;

    /**
     * Default server constructor.
     */
    private GameServer() {

        // Take note of start time
        startTime = System.currentTimeMillis();

        // Set default port
        serverPort = DEFAULT_SERVER_PORT;

        ServerProperties.setUpFromFile();

        setServerPort(ServerProperties.getInstance().getServerPort());
    }

    /**
     * Return the single instance of this server.
     *
     * @return
     */
    public static GameServer getInstance() {
        if (instance == null) {
            instance = new GameServer();
        }
        return instance;
    }

    /**
     * Initialise the DG server.
     *
     * @throws ServerDataException
     */
    public void init() {

        // Load the correct type of data connection
        this.dataConnection = ServerDataFactory.getInstance();

    }

    /**
     * <p>This method parses the commands handed in from the command prompt.
     * For example:</p>
     *
     * <code>-port=1234</code>
     *
     * <p>This sets the port for the server to listen on to 1234. More complex
     * servers should over write this method and handle its specific commands
     * here.</p>
     *
     * @param args     Additional arguments from the command line.
     */
    public void parseCommandLineArguments(String[] args) {
        if (args != null) {
            for (int i = 0; i < args.length; i++) {
                String argument = args[i];

                // Read the port if specified
                if (argument.startsWith("-port=")) {
                    int pos = argument.indexOf("=");
                    try {
                        int portNum = Integer.parseInt(argument.substring(pos +
                                1));
                        setServerPort(portNum);
                    } catch (NumberFormatException nfEx) {
                        usage();
                    }
                }
                // Read the language
                else if (argument.startsWith("-lang=")) {
                    int pos = argument.indexOf("=");
                    try {
                        String lang = argument.substring(pos + 1);
                        GameGlobals.setLocale(lang);
                    } catch (Exception nfEx) {
                        usage();
                    }
                }
            }
        }

        // Print port and language
        Locale l = GameGlobals.getLocale();
        System.out.println(labels.get("server.port") + ":\t\t" + getServerPort());
        System.out.println(labels.get("language") + ":\t\t" + l.getLanguage() +
                           " (" +
                           l.getDisplayLanguage() + ")");
    }

    /**
     * Prints out the usage of the server.
     */
    private void usage() {
        labels = ServerLabels.getInstance();
        System.out.println(labels.get("dg.server.version") + ": " +
                           IGame.VERSION);
        System.out.println("\n" + labels.get("usage") + ":");
        System.out.println("\n\tjava com.dg.gaming.server.GameServer [" +
                           labels.get("additional.arguments") + "]");
        System.out.println("\n" + labels.get("arguments") + ":");
        System.out.println("\t-port=x         x=" +
                           labels.get("port.number.default.1790"));
        System.out.println("\t-lang=x         x=" +
                           GameGlobals.SUPPORTED_LANGS);
    }

    /**
     * Run method which runs a new server and listens for clients on the
     * specified port.
     */
    public void run() {

        try {

            System.out.println(labels.get(
                    "dg.games.server.listening.on.port") + ": " + serverPort);

            // Show time server has started
            NumberFormat nf = NumberFormat.getInstance();
            nf.setGroupingUsed(true);
            long timeStarted = System.currentTimeMillis() - startTime;
            System.out.println("\n" +
                               labels.get("started.in",
                                          new String[] {nf.format(timeStarted)}));
            System.setProperty(
                    "org.xsocket.connection.client.readbuffer.usedirect",
                    "true");
            System.setProperty(
                    "org.xsocket.connection.server.readbuffer.usedirect",
                    "true");
            int messageQueueSize = 100;
            int threadqueuecapacity = Runtime.getRuntime().availableProcessors() *
                                      2 +
                                      1;
            DBManager.init();
            long keepAliveTime = 100;
            TimeUnit unit = TimeUnit.SECONDS;
            ThreadPoolExecutor executor = new ThreadPoolExecutor(
                    threadqueuecapacity,
                    threadqueuecapacity, keepAliveTime,
                    unit,
                    new ArrayBlockingQueue(messageQueueSize));
            System.setProperty(
                    "org.xsocket.connection.server.workerpoolSize ",
                    "" + threadqueuecapacity);

            WorkerQueue wq = new WorkerQueue(executor);
//            SSLContext context = SSLContext.getInstance("SSL");
//            context.init(null, getTrustManager(), new java.security.SecureRandom());

            Server srv = new Server(serverPort, new ServerConnectionHandler(wq));
            srv.setWorkerpool(executor);

            srv.setFlushmode(FlushMode.ASYNC);
            srv.setAutoflush(false);
            ConnectionUtils.registerMBean(srv);
            srv.run();
        } catch (BindException bindEx) {
            System.out.println(labels.get(
                    "dg.server.already.running.on.port") + ": " + serverPort);
            System.exit(0);
        } catch (Exception genEx) {
            genEx.printStackTrace();
        }

        // Close the server down again
        try {
            System.out.println(labels.get("dg.games.server.closing") + ": " +
                               serverPort);

        } catch (Exception ioEx) {}
    }


    /**
     * Set the server port.
     *
     * @param serverPort
     */
    public void setServerPort(int serverPort) {
        this.serverPort = serverPort;
    }

    /**
     * Return the server port.
     *
     * @return
     */
    public int getServerPort() {
        return serverPort;
    }

    /**
     * Set the link to the master server.
     *
     * @param linkToMasterServer
     */
    public void setLinkToMasterServer(boolean linkToMasterServer) {
        this.linkToMasterServer = linkToMasterServer;
    }

    /**
     * Return true if this server wishes to try and link to the
     * master DG game server.
     *
     * @return
     */
    public boolean isLinkToMasterServer() {
        return linkToMasterServer;
    }


    /**
     * Return the connection to the users.
     *
     * @return
     */
    public IServerData getServerData() {
        return dataConnection;
    }


    /**
     * Main method which creates a single instance of the server,
     * parses the commandline arguments and then runs the server.
     *
     * @param args     Additional arguments from command line.
     */
    public static void main(String[] args) {
        // Initialise the server
        GameServer server = getInstance();

        // Parse the command line arguments
        server.parseCommandLineArguments(args);

        // Setup server and run the server
        server.init();
        server.run();
    }

    private static class DefaultThreadFactory implements ThreadFactory {
        private static final AtomicInteger poolNumber = new AtomicInteger(1);
        private final AtomicInteger threadNumber = new AtomicInteger(1);
        private final String namePrefix;

        DefaultThreadFactory() {
            namePrefix = "xServerPool-" + poolNumber.getAndIncrement() +
                         "-thread-";
        }

        public Thread newThread(Runnable r) {
            Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
            if (t.isDaemon()) {
                t.setDaemon(false);
            }
            if (t.getPriority() != Thread.NORM_PRIORITY) {
                t.setPriority(Thread.NORM_PRIORITY);
            }
            return t;
        }
    }


    private static TrustManager[] getTrustManager() {

        TrustManager[] trustAllCerts = new TrustManager[] {

                                       new X509TrustManager() {

            public java.security.cert.X509Certificate[] getAcceptedIssuers() {

                return null;

            }

            public void checkClientTrusted(

                    java.security.cert.X509Certificate[] certs, String authType) {

            }

            public void checkServerTrusted(

                    java.security.cert.X509Certificate[] certs, String authType) {

            }

        }

        } ;

        return trustAllCerts;

    }
}
